package modlgen

import (
	l4g "code.google.com/p/log4go"
	"fmt"
	"github.com/davecgh/go-spew/spew"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

type mdoTypeDefList []*mdoTypeDef

func (l mdoTypeDefList) Len() int           { return len(l) }
func (l mdoTypeDefList) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
func (l mdoTypeDefList) Less(i, j int) bool { return l[i].typeId < l[j].typeId }

type mdoTypeDefSet struct {
	byId       map[int]*mdoTypeDef
	sortedById mdoTypeDefList
	byName     map[string]*mdoTypeDef
}

func newMdoTypeDefSet() *mdoTypeDefSet {
	set := new(mdoTypeDefSet)
	set.byId = make(map[int]*mdoTypeDef, 1000)
	set.byName = make(map[string]*mdoTypeDef, 1000)
	return set
}

func (tds *mdoTypeDefSet) add(typeDef *mdoTypeDef) error {
	l4g.Info("Adding type %s, id: %d", typeDef.name, typeDef.typeId)
	l4g.Debug(func() string {
		return spew.Sdump(*typeDef)
	})
	if existingTypeDef, present := tds.byId[typeDef.typeId]; present {
		panic(fmt.Sprintf("Type id %d redefined on line %d of file %s. Previous definition on line %d of file %s",
			typeDef.typeId, typeDef.definedOnLineNum, typeDef.definedInFileName, existingTypeDef.definedOnLineNum, existingTypeDef.definedInFileName))
	}
	tds.byId[typeDef.typeId] = typeDef
	if existingTypeDef, present := tds.byName[typeDef.name]; present {
		panic(fmt.Sprintf("Type name %s redefined on line %d of file %s. Previous definition on line %d of file %s",
			typeDef.name, typeDef.definedOnLineNum, typeDef.definedInFileName, existingTypeDef.definedOnLineNum, existingTypeDef.definedInFileName))
	}
	tds.byName[typeDef.name] = typeDef
	return nil
}

func (tds *mdoTypeDefSet) resolveHierarchy() {
	present := false
	for _, typeDef := range tds.byId {
		l4g.Debug("Resolving Hierarchy for %s", typeDef.name)
		if typeDef.extendsName != "" {
			if typeDef.extends, present = tds.byName[typeDef.extendsName]; !present {
				panic(fmt.Sprintf("Type %s defined on line %d of file %s cannot extend undefined type %s", typeDef.name, typeDef.definedOnLineNum, typeDef.definedInFileName, typeDef.extendsName))
			}
		}
		typeDef.checkHierarchyForDuplicateAttributeNames()
		typeDef.resolveMethodHierarchy()
	}
}

func (tds *mdoTypeDefSet) generate(baseDir, srcPkgDir string) {
	l4g.Debug("Generating from source %s", srcPkgDir)
	// decompose source package dir into useful components
	pkgPath, pkgName := filepath.Split(srcPkgDir)
	destPath := filepath.Join(baseDir, "artifacts", pkgPath, fmt.Sprintf("generated_%s", pkgName))
	l4g.Debug("Creating %s...", destPath)
	os.MkdirAll(destPath, 0700)
	tds.sortedById = make(mdoTypeDefList, len(tds.byId))
	i := 0
	for _, v := range tds.byId {
		tds.sortedById[i] = v
		i++
	}
	sort.Sort(tds.sortedById)
	for _, typeDef := range tds.sortedById {
		typeDef.generate(destPath, pkgName)
	}
	fileInfoList, err := ioutil.ReadDir(destPath)
	if err != nil {
		panic(fmt.Sprintf("Couldn't read filelist in %s. Error: %s", destPath, err))
	}
	for _, fileInfo := range fileInfoList {
		if fileInfo.IsDir() {
			continue
		}
		if tds.byName[strings.Replace(fileInfo.Name(), ".go", "", -1)] == nil {
			fmt.Printf("--------------- %s is obsolete! You should do a clean build to remove its derived objects ----------------\n", fileInfo.Name())
		}
	}
}
